#include <stdlib.h>
#include <stdio.h>
#include <memory.h>

#include "pbmemory.h"
#include "../utils/cmdline.h"



static void Alloc(int count, int size, IResultWriter *results)
{
    char **pointers = (char **)malloc(count * sizeof(char*));

    double start_alloc = os_GetTime();

    for (int i=0; i<count; i++)
        pointers[i] = (char*)malloc(size);

    double end_alloc = os_GetTime(), &start_commit = end_alloc;

    for (int i=0; i<count; i++)
        memset(pointers[i], 'a', size);

    double end_commit = os_GetTime(), &start_free = end_commit;

    for (int i=0; i<count; i++)
        free(pointers[i]);

    double end_free = os_GetTime();

    free(pointers);

    const char *kbmb = "";

    if (size > 1024)
    {
        size /= 1024;
        kbmb = "kb";
    }                    

    char buff[128];
    sprintf(buff, "alloc%dx%d%s", count, size, kbmb);
    results->WriteNode(buff, (float)(end_alloc - start_alloc));
    sprintf(buff, "commit%dx%d%s", count, size, kbmb);
    results->WriteNode(buff, (float)(end_commit - start_commit));
    sprintf(buff, "free%dx%d%s", count, size, kbmb);
    results->WriteNode(buff, (float)(end_free - start_free));
}


static void ReAlloc(int count, int size, IResultWriter *results)
{
    char **pointers = (char **)malloc(count * sizeof(char*));

    for (int i=0; i<count; i++)
        pointers[i] = (char*)malloc(size);

    double start_realloc = os_GetTime();

    for (int i=0; i<count; i++)
        pointers[i] = (char*)realloc(pointers[i], size * 2);

    double end_realloc = os_GetTime();

    for (int i=0; i<count; i++)
        pointers[i] = (char*)realloc(pointers[i], size);

    double end_realloc2 = os_GetTime();

    for (int i=0; i<count; i++)
        memset(pointers[i], 'a', size);

    for (int i=0; i<count; i++)
        free(pointers[i]);

    free(pointers);

    char buff[128];
    sprintf(buff, "realloc%dx%dto%d", count, size, size*2);
    results->WriteNode(buff, (float)(end_realloc - start_realloc));
    sprintf(buff, "realloc%dx%dto%d", count, size*2, size);
    results->WriteNode(buff, (float)(end_realloc2 - end_realloc));
}


static void EatMemory(const char *name, size_t size_in_megabytes, IResultWriter *results)
{
    uint8_t *p = NULL;

    try
    {
        double start = os_GetTime();

        p = (uint8_t*)malloc(size_in_megabytes * 1024*1024);

        for (size_t i=0; i<size_in_megabytes*1024*1024; i += 1024)   // 1k-ontkent beleirunk 1 byte-t -> valoszinuleg 1-1 page commitalas
            p[i] = 0xfa;

        free(p);
        p = NULL;

        double end = os_GetTime();

        results->WriteNode(name, (float)(end - start));
    }
    catch (...)
    {
        results->WriteNode(name, 10000.0f);
    }

    if (p)
        free(p);
}


static void MemSet(int count, int size, IResultWriter *results)
{   
    uint8_t *p = (uint8_t*)malloc(size);
            
    if (!p)
        return ;

    double start = os_GetTime();

    for (int i=0; i<count; i++)
        memset(p, i & 0xff, size); 

    double end = os_GetTime();

    free(p);

    char buff[128];
    const char *kbmb = "";

    if (size > 1024*1024)
    {
        size /= 1024*1024;
        kbmb = "mb";
    }
    else if (size > 1024)
    {
        size /= 1024;
        kbmb = "kb";
    }
                    
    sprintf(buff, "memset%dx%d%s", count, size, kbmb);
    results->WriteNode(buff, (float)(end - start));
}
    

static void MemCopy(int count, int size, IResultWriter *results)
{
    uint8_t *dst = (uint8_t*)malloc(size);    
            
    if (!dst)
        return ;

    uint8_t *src = (uint8_t*)malloc(size);    
            
    if (!src)
    {
        free(dst);
        return ;
    }

    memset(src, 0xab, size);

    double start = os_GetTime();

    for (int i=0; i<count; i++)
        memcpy(dst, src, size); 

    double end = os_GetTime();

    free(dst);
    free(src);

    char buff[128];
    const char *kbmb = "";

    if (size > 1024*1024)
    {
        size /= 1024*1024;
        kbmb = "mb";
    }
    else if (size > 1024)
    {
        size /= 1024;
        kbmb = "kb";
    }
                   
    sprintf(buff, "memcopy%dx%d%s", count, size, kbmb);
    results->WriteNode(buff, (float)(end - start));
}


void CMemoryBenchmark::Run(IResultWriter *results, const CCmdlineArgs *args)
{
    ReAlloc(102400, 15, results);
    ReAlloc(102400, 128, results);
    ReAlloc(10240, 2048, results);

    Alloc(102400, 15, results);
    Alloc(102400, 128, results);

    int mem = (int)(os_GetPhysicalMemory() / (1024*1024));
    bool force = false; //args->Check("-fa");

    if (force || mem >= 200)
    {
        Alloc(102400, 1024, results);         // 100MB
        Alloc(10240,  1024*10, results);
        Alloc(1024,   1024*100, results);
    }

    MemSet(10240000, 16, results);
    MemSet(10240, 128*1024, results);
    MemSet(32, 4*1024*1024, results);

    MemCopy(10240000, 16, results);
    MemCopy(10240, 128*1024, results);
    MemCopy(32, 4*1024*1024, results);


#if !defined(DEBUG) && !defined(_DEBUG)
    EatMemory("eatmem25%", (size_t)(mem * 0.25), results);
    EatMemory("eatmem50%", (size_t)(mem * 0.50), results);

    if (force)
    {
        EatMemory("eatmem75%", (size_t)(mem * 0.75), results);
        //EatMemory("eatmem90%", (size_t)(mem * 0.90), results);
    }
#endif
}


